
package com.bangcommunity.bbframe.springredis;

import com.bangcommunity.bbframe.common.proxy.DelegateExecutorUtils;
import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import com.bangcommunity.bbframe.config.QtMonitorConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class RedisRepository<V> implements ISpringRedisCache<V> {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private String prefix = "";
    private RedisTemplate<String, V> redisTemplate;

    @Override
    public RedisTemplate<String, V> getRedisTemplate() {

        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, V> redisTemplate) {

        this.redisTemplate = redisTemplate;
    }

    public String getPrefix() {

        return prefix;
    }

    public void setPrefix(String prefix) {

        this.prefix = prefix;
    }

    private BoundValueOperations<String, V> getBoundValueOps(String key) {

        return redisTemplate.boundValueOps(key);
    }

    private BoundZSetOperations<String, V> getBoundZSetOps(String key) {

        return redisTemplate.boundZSetOps(key);
    }

    private BoundSetOperations<String, V> getBoundSetOps(String key) {

        return redisTemplate.boundSetOps(key);
    }

    private BoundListOperations<String, V> getBoundListOps(String key) {

        return redisTemplate.boundListOps(key);
    }

    private <HK, HV> BoundHashOperations<String, HK, HV> getBoundHashOps(String key) {
        return redisTemplate.boundHashOps(key);
    }
    // Key

    @Override
    public void del(final String key) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "del"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                redisTemplate.delete(key);
                return null;
            }
        });
    }

    @Override
    public void del(final Collection<String> keys) {

        this.delegateExecute(keys, new InvokerMethodInfo(false, "delkeys"), new DelegateExecutor<Collection<String>>() {

            @Override
            public <T> T doExecute(Collection<String> keys) {
                Set<String> realKeys = null;
                if (null != keys) {
                    realKeys = new HashSet<>();
                    for (String key : keys) {
                        if (!key.startsWith(prefix)) {
                            key = wrapPrefix(key);
                        }
                        realKeys.add(key);
                    }
                }
                redisTemplate.delete(realKeys);
                return null;
            }
        });
    }

    @Override
    public Boolean exists(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "exists"), new DelegateExecutor<String>() {

            @Override
            public Boolean doExecute(String key) {

                return redisTemplate.hasKey(key);
            }
        });
    }

    @Override
    public Boolean expire(final String key, final long timeout, final TimeUnit unit) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "expire"), new DelegateExecutor<String>() {

            @Override
            public Boolean doExecute(String key) {

                return redisTemplate.expire(key, timeout, unit);
            }
        });
    }

    @Override
    public void expireAt(final String key, final Date date) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "expireAt"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                redisTemplate.expireAt(key, date);
                return null;
            }
        });
    }

    @Override
    public Set<String> keys(final String pattern) {

        return this.delegateExecute(pattern, new InvokerMethodInfo(true, "keys"), new DelegateExecutor<String>() {

            @Override
            public Set<String> doExecute(String key) {

                return redisTemplate.keys(key);
            }

        });

    }

    @Override
    public String type(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "type"), new DelegateExecutor<String>() {

            @Override
            public String doExecute(String key) {

                return redisTemplate.type(key).code();
            }

        });
    }

    @Override
    public V get(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "get"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                return ops.get();
            }
        });
    }

    @Override
    public void setIncr(final String key, final Long value, final long timeout, final TimeUnit unit) {
        this.setString(key, String.valueOf(value), timeout, unit);
    }

    @Override
    public void setString(final String key, final String value, final long timeout, final TimeUnit unit) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "setString"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
                byte[] execute = execute(new RedisCallback<byte[]>() {

                    public byte[] doInRedis(RedisConnection connection) {
                        potentiallyUsePsetEx(connection);
                        connection.set(rawKey, redisTemplate.getStringSerializer().serialize(value));
                        return null;
                    }

                    public void potentiallyUsePsetEx(RedisConnection connection) {

                        if (!TimeUnit.MILLISECONDS.equals(unit) || !failsafeInvokePsetEx(connection)) {
                            connection.setEx(rawKey, TimeoutUtils.toSeconds(timeout, unit), redisTemplate.getStringSerializer().serialize(String.valueOf(value)));
                        }
                    }

                    private boolean failsafeInvokePsetEx(RedisConnection connection) {

                        boolean failed = false;
                        try {
                            connection.pSetEx(rawKey, timeout, redisTemplate.getStringSerializer().serialize(String.valueOf(value)));
                        } catch (UnsupportedOperationException e) {
                            // in case the connection does not support pSetEx return false to allow fallback to other operation.
                            failed = true;
                        }
                        return !failed;
                    }
                });
                return null;
            }
        });
    }

    @Override
    public void setIncr(final String key, final Long value) {
        this.setString(key, String.valueOf(value));
    }

    @Override
    public void setString(final String key, final String value) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "setString"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
                byte[] execute = execute(new RedisCallback<byte[]>() {

                    public byte[] doInRedis(RedisConnection connection) {
                        connection.set(rawKey, redisTemplate.getStringSerializer().serialize(value));
                        return null;
                    }
                });
                return null;
            }
        });
    }

    @Override
    public Long getIncr(final String key) {
        String string = this.getString(key);
        if (null == string) {
            return null;
        }
        return Long.parseLong(string);
    }

    @Override
    public String getString(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "getString"), new DelegateExecutor<String>() {

            @Override
            public String doExecute(String key) {

                final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
                byte[] execute = execute(new RedisCallback<byte[]>() {

                    public byte[] doInRedis(RedisConnection connection) {
                        return connection.get(rawKey);
                    }
                });
                return redisTemplate.getStringSerializer().deserialize(execute);
            }

        });
    }

    @Override
    public V getSet(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "getSet"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                return ops.getAndSet(value);
            }
        });
    }

    @Override
    public Long incr(final String key, final long delta) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "incr"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {
//                JedisConnection connection = (JedisConnection)redisTemplate.getConnectionFactory().getConnection();
//                Jedis jedis = connection.getNativeConnection();
//                return jedis.incrBy(key,delta);
//                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                ValueOperations<String, V> ops = redisTemplate.opsForValue();
                return ops.increment(key, delta);
            }
        });
    }

    @Override
    public void set(final String key, final V value) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "set"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                ops.set(value);
                return null;
            }
        });
    }

    @Override
    public void set(final String key, final V value, final long timeout, final TimeUnit unit) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "set"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                ops.set(value, timeout, unit);
                return null;
            }
        });
    }

    @Override
    public boolean setNX(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "setNX"), new DelegateExecutor<String>() {

            @Override
            public Boolean doExecute(String key) {

                BoundValueOperations<String, V> ops = getBoundValueOps(key);
                Boolean aBoolean = ops.setIfAbsent(value);
                return null != aBoolean && aBoolean;
            }
        });
    }

    // Hash
    @Override
    public void hDel(final String key, final Object... hKeys) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "hDel"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                ops.delete(hKeys);
                return null;
            }
        });
    }

    @Override
    public Boolean hExists(final String key, final String hKeys) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hExists"), new DelegateExecutor<String>() {

            @Override
            public Boolean doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.hasKey(hKeys);
            }
        });
    }

    @Override
    public Map<String, V> hGet(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hGet"), new DelegateExecutor<String>() {

            @Override
            public Map<String, V> doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.entries();
            }
        });
    }

    @Override
    public V hGet(final String key, final String hKey) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hGet"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.get(hKey);
            }
        });
    }

    @Override
    public Set<String> hKeys(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hKeys"), new DelegateExecutor<String>() {

            @Override
            public Set<String> doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.keys();
            }
        });
    }

    @Override
    public Long hLen(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hLen"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.size();
            }
        });
    }

    @Override
    public void hSet(final String key, final String hk, final V hv) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "hSet"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                ops.put(hk, hv);
                return null;
            }
        });
    }

    @Override
    public void hSet(final String key, final Map<String, V> map) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "hSet"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                ops.putAll(map);
                return null;
            }
        });
    }

    @Override
    public List<V> hVals(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "hVals"), new DelegateExecutor<String>() {

            @Override
            public List<V> doExecute(String key) {

                BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
                return ops.values();
            }
        });
    }

    // List
    @Override
    public V lIndex(final String key, final long index) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "lIndex"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.index(index);
            }
        });
    }

    @Override
    public void lInsert(final String key, final long index, final V value) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "lInsert"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                ops.set(index, value);
                return null;
            }
        });
    }

    @Override
    public Long lLen(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "lLen"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.size();
            }
        });
    }

    @Override
    public V lPop(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "lPop"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.leftPop();
            }
        });
    }

    @Override
    public V lPop(final String key, final long timeout, final TimeUnit unit) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "lPop"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.leftPop(timeout, unit);
            }
        });
    }

    @Override
    public Long lPush(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "lPush"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.leftPush(value);
            }
        });
    }

    @Override
    public Long lPushAll(final String key, final V... values) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "lPushAll"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.leftPushAll(values);
            }
        });
    }

    @Override
    public List<V> lRange(final String key, final long start, final long end) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "lRange"), new DelegateExecutor<String>() {

            @Override
            public List<V> doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.range(start, end);
            }
        });
    }

    @Override
    public Long lRem(final String key, final long index, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "lRem"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.remove(index, value);
            }
        });
    }

    @Override
    public void lSet(final String key, final long index, final V value) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "lSet"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundListOperations<String, V> ops = getBoundListOps(key);
                ops.set(index, value);
                return null;
            }
        });
    }

    @Override
    public void ltrim(final String key, final long start, final long end) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "ltrim"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {
                BoundListOperations<String, V> ops = getBoundListOps(key);
                ops.trim(start, end);
                return null;
            }
        });
    }

    @Override
    public Long rPush(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "rPush"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {
                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.rightPush(value);
            }
        });
    }

    @Override
    public Long rPushAll(final String key, final V... values) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "rPushAll"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {
                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.rightPushAll(values);
            }
        });
    }

    @Override
    public V rPop(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "rPop"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {
                BoundListOperations<String, V> ops = getBoundListOps(key);
                return ops.rightPop();
            }
        });
    }

    // Set
    @Override
    public Long sAdd(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "sAdd"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {
                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.add(value);
            }
        });
    }

    @Override
    public Set<V> sDiff(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "sDiff"), new DelegateExecutor<String>() {

            @Override
            public Set<V> doExecute(String key) {
                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.diff(key);
            }
        });
    }

    @Override
    public Set<V> sMembers(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "sMembers"), new DelegateExecutor<String>() {

            @Override
            public Set<V> doExecute(String key) {
                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.members();
            }
        });
    }

    @Override
    public Boolean sIsMember(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "sIsMember"), new DelegateExecutor<String>() {

            @Override
            public Boolean doExecute(String key) {

                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.isMember(value);
            }
        });
    }

    @Override
    public V sPop(final String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "sPop"), new DelegateExecutor<String>() {

            @Override
            public V doExecute(String key) {

                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.pop();
            }
        });
    }

    @Override
    public Long sRem(final String key, final V value) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "sRem"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.remove(value);
            }
        });
    }

    @Override
    public Long sCard(String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "sCard"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundSetOperations<String, V> ops = getBoundSetOps(key);
                return ops.size();
            }
        });
    }

    // SortedSet
    @Override
    public void zAdd(final String key, final V value, final double score) {

        this.delegateExecute(key, new InvokerMethodInfo(false, "zAdd"), new DelegateExecutor<String>() {

            @Override
            public <T> T doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                ops.add(value, score);
                return null;
            }
        });
    }

    @Override
    public Set<V> zRange(final String key, final long start, final long end) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "zRange"), new DelegateExecutor<String>() {

            @Override
            public Set<V> doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                return ops.range(start, end);
            }
        });
    }

    @Override
    public Long zRem(final String key, final Object... values) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "zRem"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                return ops.remove(values);
            }
        });
    }

    @Override
    public Long zRemByRange(final String key, final long start, final long end) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "zRemByRange"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                return ops.removeRange(start, end);
            }
        });
    }

    @Override
    public Long zRemByScore(final String key, final double start, final double end) {

        return this.delegateExecute(key, new InvokerMethodInfo(false, "zRemByScore"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                return ops.removeRangeByScore(start, end);
            }
        });
    }

    @Override
    public Long zCard(String key) {

        return this.delegateExecute(key, new InvokerMethodInfo(true, "zCard"), new DelegateExecutor<String>() {

            @Override
            public Long doExecute(String key) {

                BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
                return ops.zCard();
            }
        });
    }

    @Override
    public <T> T execute(final RedisScript<T> script, final List<String> keys, final Object... args) {

        return this.delegateExecute("l_u_a", new InvokerMethodInfo(false, "executeLua"),
                new DelegateExecutor<String>() {
                    @Override
                    public T doExecute(String key) {

                        return redisTemplate.execute(script, keys, args);
                    }
                });

    }

    @Override
    public <T> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer,
                         final RedisSerializer<T> resultSerializer, final List<String> keys, final Object... args) {

        return this.delegateExecute("l_u_a", new InvokerMethodInfo(false, "executeLua"),
                new DelegateExecutor<String>() {
                    @Override
                    public T doExecute(String key) {

                        return redisTemplate.execute(script, argsSerializer, resultSerializer, keys, args);
                    }
                });

    }

    @Override
    public <T> T execute(final RedisCallback<T> action) {

        return this.delegateExecute("RedisCallback", new InvokerMethodInfo(false, "execute"),
                new DelegateExecutor<String>() {
                    @Override
                    public T doExecute(String key) {

                        return redisTemplate.execute(action);
                    }
                });

    }

    @Override
    public <T> T execute(final SessionCallback<T> session) {

        return this.delegateExecute("SessionCallback", new InvokerMethodInfo(false, "execute"),
                new DelegateExecutor<String>() {
                    @Override
                    public T doExecute(String key) {

                        return redisTemplate.execute(session);
                    }
                });

    }

    public <K, V> V delegateExecute(K key, InvokerMethodInfo methodInfo, DelegateExecutor<K> delegateExecutor) {
        logger.debug("========== monitor ====  springredis  =========={}", methodInfo);
        if (null != delegateExecutor) {

            return DelegateExecutorUtils
                    .delegateExecute(new com.bangcommunity.bbframe.common.proxy.DelegateExecutor<V>() {
                        @Override
                        public void doBefore(Map<String, Object> context) {
                            long startTime = System.currentTimeMillis();
                            context.put("startTime", startTime);
                        }

                        @Override
                        public V doExecute(Map<String, Object> context) {
                            K realKey = delegateExecutor.wrapKey(key);
                            context.put("realKey", realKey);
                            return delegateExecutor.doExecute(realKey);
                        }

                        @Override
                        public boolean doException(Map<String, Object> context, Exception e) {
                            return true;
                        }

                        @Override
                        public void doFinal(Map<String, Object> context, V v, Exception e) {
                            if (QtMonitorConfig.redis_on) {
                                long endTime = System.currentTimeMillis();
                                K realKey = (K) context.get("realKey");
                                Long startTime = (Long) context.get("startTime");
                                monitorRedis(methodInfo, key, realKey, v, null != startTime ? endTime - startTime : -1);
                            }
                        }
                    });
        }
        return null;
    }

    public class InvokerMethodInfo implements Serializable {
        private static final long serialVersionUID = 4709588644668370278L;
        private boolean isRead = false;
        private String methodName;

        public InvokerMethodInfo(boolean isRead, String methodName) {
            this.isRead = isRead;
            this.methodName = methodName;
        }

        public boolean isRead() {
            return isRead;
        }

        public void setIsRead(boolean isRead) {
            this.isRead = isRead;
        }

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    }

    public abstract class DelegateExecutor<K> {

        public abstract <T> T doExecute(K realKey);

        public K wrapKey(K key) {
            if (key instanceof String) {
                return (K) (wrapPrefix((String) key));
            }
            return key;
        }
    }

    @Override
    public String wrapPrefix(String key) {
        if (null == key || key.trim().length() == 0) {
            return null;
        }
        if (StringUtils.isBlank(prefix)) {
            return key;
        }
        return new StringBuilder().append(prefix).append(":").append(key).toString();
    }

    @Override
    public void destroy() {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        if (null != connectionFactory) {
            ((JedisConnectionFactory) connectionFactory).destroy();
        }
    }

    /**
     * TODO
     */
    public <K, V> void monitorRedis(InvokerMethodInfo methodInfo, K key, K realKey, V t, long timeElapseMs) {
        logger.debug("redis cache: methodInfo={},key={},timeElapseMs={}", methodInfo, realKey, timeElapseMs);
        if (key instanceof String) {

        } else {

        }
    }

}
